﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using Tao.OpenGl;

namespace Imagiality.MeshProcessing
{
    public class Canvas3D
    {
        private GLControl glControl;
        private int[] viewPort;
        private Scene scene;
        private ViewCamera viewCamera;

        public Canvas3D(GLControl control, Scene scene, ViewCamera camera)
        {
            
            this.scene = scene;
            glControl = control;
            viewCamera = camera;
            setupGLControlEvents();
            initializeViewport();
        }

        private void initializeViewport()
        {
            viewPort = new int[] {0,0,glControl.Width, glControl.Height};
            GL.ClearColor(Color.Black);
            GL.Enable(EnableCap.DepthTest);
            GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
            updateViewport();
        }

        private void updateViewport()
        {
            float aspect = glControl.Width / (float)glControl.Height;
            GL.Viewport(viewPort[0], viewPort[1], viewPort[2], viewPort[3]);
            OpenTK.Matrix4 perspective = OpenTK.Matrix4.CreatePerspectiveFieldOfView(1, aspect, 1, 64);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref Matrix4.Identity);
            GL.MultMatrix(ref perspective);
            repaint();
        }

        private void repaint()
        {
            viewCamera.Render(scene);
            glControl.SwapBuffers();
        }

        private void setupGLControlEvents()
        {
            glControl.Paint += new System.Windows.Forms.PaintEventHandler(glControl_Paint);
            glControl.KeyPress += new System.Windows.Forms.KeyPressEventHandler(glControl_KeyPress);
            glControl.MouseDown += new System.Windows.Forms.MouseEventHandler(glControl_MouseDown);
            glControl.MouseMove += new System.Windows.Forms.MouseEventHandler(glControl_MouseMove);
            glControl.MouseUp += new System.Windows.Forms.MouseEventHandler(glControl_MouseUp);
            glControl.MouseWheel += new System.Windows.Forms.MouseEventHandler(glControl_MouseWheel);
            glControl.Resize += new System.EventHandler(glControl_Resize);
            glControl.MouseClick += new System.Windows.Forms.MouseEventHandler(glControl_MouseClick);
        }


        private void select(float x, float y, float width, float height)
        {
            int BUFSIZE = 100000;
            int[] selectBuf = new int[BUFSIZE];
            GL.SelectBuffer(BUFSIZE, selectBuf);

            GL.RenderMode(RenderingMode.Select);

            GL.MatrixMode(MatrixMode.Projection);
            GL.PushMatrix();

            int[] viewport = new int[4];
            GL.GetInteger(GetPName.Viewport, viewport);
            double[] doubleArray = new double[16];
            GL.GetDouble(GetPName.ProjectionMatrix, doubleArray);
            GL.LoadMatrix(ref Matrix4.Identity);

            Glu.gluPickMatrix(x, y, width, height, viewport);
            GL.MultMatrix(doubleArray);

            GL.InitNames();
           // GL.PushName(0);
            viewCamera.Render(scene);
           // GL.PopName();

            GL.MatrixMode(MatrixMode.Projection);
            GL.PopMatrix();
            int hits = GL.RenderMode(RenderingMode.Render);


            IList<Renderable> renderableParents = new RenderableParentsSceneQuery().Execute((IList<SceneObject>)scene.Objects.ToArray());

            IList<PathBasedByIDQuery<SceneObject>> queries = processHits(hits, selectBuf);
            foreach (PathBasedByIDQuery<SceneObject> query in queries)
            {
                IList<SceneObject> results = (IList<SceneObject>)query.Execute((IList<SceneObject>)renderableParents.ToArray());
            }
         
            GL.MatrixMode(MatrixMode.Modelview);
        
        }

        private IList<PathBasedByIDQuery<SceneObject>>  processHits(int hits, int[] selectedBuf)
        {
            IDictionary<Queue<uint>, ISet<uint>> pathTargetDict = new Dictionary<Queue<uint>, ISet<uint>>();
            
            int i = 0;
            while(hits > 0)
            {
                int pathDepth = selectedBuf[i];
                
                // skip visual depth data
                i = i + 3;

                Queue<uint> path = new Queue<uint>();
                uint target = 0;
                while(pathDepth > 0)
                {
                    uint objID = (uint)selectedBuf[i];
                    i++;

                    if (pathDepth > 1)
                    {
                        path.Enqueue(objID);
                    }
                    else 
                    {
                        target = objID;
                    }

                    pathDepth--;
                }

                Queue<uint> foundQueue = null;
                for (int j = 0; j < pathTargetDict.Count && foundQueue == null; j++)
                {
                    if (pathTargetDict.Keys.ElementAt(j).SequenceEqual(path))
                    {
                        foundQueue = pathTargetDict.Keys.ElementAt(j);
                    }
                }

                if (foundQueue == null)
                {
                    ISet<uint> targetSet = new HashSet<uint>();
                    targetSet.Add(target);
                    pathTargetDict.Add(path, targetSet);
                }
                else
                {
                    pathTargetDict[foundQueue].Add(target);
                }
                        

                hits--;
            }

            IList<PathBasedByIDQuery<SceneObject>> queries = new List<PathBasedByIDQuery<SceneObject>>();

            for (int k = 0; k < pathTargetDict.Keys.Count; k++)
            {
                Queue<uint> path = pathTargetDict.Keys.ElementAt(k);
                queries.Add(new PathBasedByIDQuery<SceneObject>(path, pathTargetDict[path]));
            }

            return queries;

        }

        // begin GL Control events
        private bool mouseDown = false;
        int previousMouseX;
        int previousMouseY;

        private void glControl_Resize(object sender, EventArgs e)
        {
            updateViewport();
        }

        private void glControl_Paint(object sender, PaintEventArgs e)
        {
            repaint();
        }

        private void glControl_MouseMove(object sender, MouseEventArgs e)
        {
            if (mouseDown)
            {
                int differanceX = e.X - previousMouseX;
                int differanceY = e.Y - previousMouseY;
                //viewCamera.ChangeAngle(differanceX * 0.25f, differanceY * 0.25f, 0);
                viewCamera.AddToLeftAndRight( differanceX * 0.1f);
                viewCamera.AddToUpAndDown(differanceY * -0.1f);
                previousMouseX = e.X;
                previousMouseY = e.Y;
                repaint();
            }    
        }

        private void glControl_MouseClick(object sender, MouseEventArgs e)
        {
            select(e.X, glControl.Height - e.Y, 2.0f, 2.0f);
        }

        private void glControl_MouseDown(object sender, MouseEventArgs e)
        {
            mouseDown = true;
            previousMouseX = e.X;
            previousMouseY = e.Y;
            viewCamera.Render(scene);
        }

        private void glControl_MouseUp(object sender, MouseEventArgs e)
        {
            mouseDown = false;
        }

        private void glControl_MouseWheel(object sender, MouseEventArgs e)
        {
            viewCamera.AddToDistanceFromObject( e.Delta / 120);
            repaint();
        }

        private void glControl_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
        {
            switch (e.KeyChar)
            {
                case 'w':
                    viewCamera.ChangePosition(0.02f, 0.0f, 0.0f);
                    break;
                case 's':
                    viewCamera.ChangePosition(-0.02f, 0.0f, 0.0f);
                    break;
                case 'e':
                    viewCamera.ChangePosition(0.0f, 0.02f, 0.0f);
                    break;
                case 'q':
                    viewCamera.ChangePosition(0.0f, -0.02f, 0.0f);
                    break;
                case 'd':
                    viewCamera.ChangePosition(0.0f, 0.0f, 0.02f);
                    break;
                case 'a':
                    viewCamera.ChangePosition(0.0f, 0.0f, -0.02f);
                    break;
                case '+':
                    viewCamera.AddToDistanceFromObject(0.2f);
                    break;
                case '-':
                    viewCamera.AddToDistanceFromObject(-0.2f);
                    break;
            }
            repaint();
        }
        // end GL Control events

    }
}
